package asia.huangxi.websocket.socket;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.model.Frame;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import com.github.dockerjava.transport.DockerHttpClient;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Tomcat的7.0.5版本开始支持WebSocket,并且实现了Java WebSocket规范 (JSR356)。
 * <p>
 * Java WebSocket 应用由一系列的webSocketEndpoint组成。 Endpoint 是一个java对象,代表webSocket链接的一端,对于
 * 服务端，我们可以视为处理具体webSocket消息的接口，就像Servlet之 与http请求一样。
 * <p>
 * 我们可以通过两种方式定义Endpoint:
 * 第一种是编程式， 即继承类javax.websocket.Endpoint并实现其方法。
 * 第二种是注解式，即定义一个POJO， 并添加@ServerEndpoint相关注解。
 * <p>
 * Endpoint实例在WebSocket握手时创建，并在客户端与服务端链接过程中有效，最后在链接关闭时结束。在Endpoint接口中明确
 * 定义了与其生命周期相关的方法，规范实现者确保生命周期的各个阶段调用实例的相关方法。生命周期方法如下:
 * onClose：当会话关闭时调用。
 * onOpen：当开启一个新的会话时调用，该方法是客户端与服务端握手成功后调用的方法。
 * onError：当连接过程中异常时调用。
 */
@ServerEndpoint(value = "/ws/container/exec" , subprotocols ={"iamhere"})
@Component
public class ChatEndpoint {
    private final Map<String, OutputStream> dockerOutputStreamMap = new ConcurrentHashMap<>();
    private final String dockerHost = "tcp://172.20.163.108:2375";
    private final String apiVersion = "v1.39";
    private String containerId;
    private DockerClient dockerClient;

    /*建立时调用*/
    @OnOpen
    public void onOpen(Session session, EndpointConfig endpointConfig) throws InterruptedException, IOException {
        this.containerId = "239e14a4d33d";
        connect();
        //获得传参
        exec(session);
        System.out.println("open");
    }

    /**
     * 接收到客户端发送的数据时调用.
     *
     * @param message 客户端发送的数据
     * @param session session对象
     * @return void
     * <p>
     * tails: 服务端如何接收客户端发送的数据呢?
     * 通过为Session 添加MessageHandler消息处理器来接收消息，当采用注解方式定义Endpoint时，我们还可以通过@OnMessage注解指定接收消息的方法。
     */
    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        System.out.println("message: " + message);
        String containerId = "239e14a4d33d";
        OutputStream out = dockerOutputStreamMap.get(containerId);
        out.write(message.getBytes(StandardCharsets.UTF_8));
        out.flush();
    }

    /**
     * 关闭时调用
     */
    @OnClose
    public void onClose(Session session) {
        System.out.println("close");
    }

    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }


    private void connect() {
        DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder()
                .withDockerHost(this.dockerHost)
                .withApiVersion(this.apiVersion)
                .build();
        DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
                .dockerHost(config.getDockerHost())
                .maxConnections(100)
                .build();
        DockerClient dockerClient = DockerClientBuilder
                .getInstance(config)
                .withDockerHttpClient(httpClient)
                .build();
        dockerClient.pingCmd().exec();
        this.dockerClient = dockerClient;
    }

    //    @Async
    public void exec(Session session/*, ChatEndpoint handler*/) throws InterruptedException, IOException {

        var id = this.dockerClient.execCreateCmd("239e14a4d33d")
                .withCmd("/bin/bash")
                .withTty(true)
                .withAttachStderr(true)
                .withAttachStdin(true)
                .withAttachStdout(true)
                .exec()
                .getId();

        PipedOutputStream pipedOutputStream = new PipedOutputStream();
        PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream);


        this.dockerOutputStreamMap.put(containerId, pipedOutputStream);
//        this.dockerClient.resizeExecCmd(id).withSize(Integer.parseInt(session.getAttributes().get("height").toString()), Integer.parseInt(session.getAttributes().get("width").toString()));
        new Thread(() -> {
            try {
                this.dockerClient.execStartCmd(id)
                        .withDetach(false)
                        .withTty(true)
                        .withStdIn(pipedInputStream)
                        .exec(new ResultCallback.Adapter<>() {
                            @Override
                            public void onNext(Frame object) {
                                try {
                                    System.out.println("object: " + object);
                                    RemoteEndpoint.Basic basicRemote = session.getBasicRemote();
                                    basicRemote.sendText(new String(object.getPayload(), StandardCharsets.UTF_8));
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }).awaitCompletion();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

    }
}
